package edu.arizona.sista.learn.util;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import edu.arizona.sista.learn.model.Interval;

public class Utils {
	
	public static final String OUTDIR = "./output/";
	
	public static enum MovieName {
		Map,
		Newspaper,
		Unknown,
		Y106,
		Y201;
		
		public static MovieName fromString(String text) {
			if (text != null) {
				for (MovieName n : MovieName.values()) {
					if (text.equalsIgnoreCase(n.toString())) {
						return n;
					}
				}
			}
		    return Unknown;
		}
	};
	
	public static enum ActorName {
		// newspaper
		WDeclining,
		MOffering,
		MWithDog,
		MGlassesStaring,
		WWalkingJeansWhite,
		WWalkingYellowShirt,
		WLastWhiteSkirt,
		
		// map
		MStriped,
		MGreen,
		WWalkingWhite,
		WWalkingGreen,
		WDisappearingGray,
		WLastSunGlassesDress,
		
		// Other
		Person0,
		Person1,
		Person2,
		Person3,
		Person4,
		Person5,
		Person6,
		Person7,
		Person8,
		Person9,
		Person10,
		Person11,
		Person12,
		Person13,
		Person14,
		Person15,
		Person16,
		Person17,
		Person18,
		Person19,
		PersonUnknown
	};
	
	
	/**
	 * Dump string to file.
	 * @param message Message to output.
	 * @param filepath Out file.
	 */
	public static void toFile(StringBuilder message, String filepath) {
		toFile(message, filepath, false);
	}
	
	/**
	 * Dump string to file.
	 * @param message Message to output
	 * @param filepath Out file.
	 * @param append Whether or not the message should be appended to file.
	 */
	public static void toFile(StringBuilder message, String filepath, 
			boolean append) {
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(filepath, append));
			out.write(message.toString());
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Get the actor's name from ID.
	 * @param clusterID The actor's ID from clustering.
	 * @return The actor's name.
	 */
	public static ActorName getPersonNameByID(String id) {
		if (id.contains("10")) 		return ActorName.Person10;
		else if (id.contains("11")) return ActorName.Person11;
		else if (id.contains("12")) return ActorName.Person12;
		else if (id.contains("13")) return ActorName.Person13;
		else if (id.contains("14")) return ActorName.Person14;
		else if (id.contains("15")) return ActorName.Person15;
		else if (id.contains("16")) return ActorName.Person16;
		else if (id.contains("17")) return ActorName.Person17;
		else if (id.contains("18")) return ActorName.Person18;
		else if (id.contains("19")) return ActorName.Person19;
		else if (id.contains("0"))	return ActorName.Person0;
		else if (id.contains("1"))	return ActorName.Person1;
		else if (id.contains("2"))	return ActorName.Person2;
		else if (id.contains("3"))	return ActorName.Person3;
		else if (id.contains("4"))	return ActorName.Person4;
		else if (id.contains("5"))	return ActorName.Person5;
		else if (id.contains("6"))	return ActorName.Person6;
		else if (id.contains("7"))	return ActorName.Person7;
		else if (id.contains("8"))	return ActorName.Person8;
		else if (id.contains("9"))	return ActorName.Person9;
		else
			return ActorName.PersonUnknown;
	}
	
	/**
	 * Get the actor's name from clustering ID.
	 * @param movie The movie.
	 * @param clusterID The actor's ID from clustering.
	 * @return The actor's name.
	 */
	public static ActorName getActorNameFromClusterID(MovieName movie,
			String clusterID) {
		String id = clusterID.trim();
		if (movie.equals(MovieName.Map)) {
			if (id.equals("Striped"))
				return ActorName.MStriped;
			else if (id.equals("Green"))
				return ActorName.MGreen;
			else if (id.equals("White"))
				return ActorName.WWalkingWhite;
			else if (id.equals("Shorts"))
				return ActorName.WWalkingGreen;
			else if (id.equals("Gray"))
				return ActorName.WDisappearingGray;
			else if (id.equals("Dress"))
				return ActorName.WLastSunGlassesDress;
		} else if (movie.equals(MovieName.Newspaper)) {
			if (id.equals("Wdress"))
				return ActorName.WDeclining;
			else if (id.equals("MBlackPants"))
				return ActorName.MOffering;
			else if (id.equals("MShorts"))
				return ActorName.MGlassesStaring;
			else if (id.equals("Wjeans&white"))
				return ActorName.WWalkingJeansWhite;
			else if (id.equals("Wyellow"))
				return ActorName.WWalkingYellowShirt;
			else if (id.equals("WwhiteSkirt"))
				return ActorName.WLastWhiteSkirt;
		} else {
			return getPersonNameByID(id);
		}
		
		return null;
	}
	
	/**
	 * Get the actor's name from pose annotation ID.
	 * @param movie The movie.
	 * @param partID The actor's ID from pose annotation.
	 * @return The actor's name.
	 */
	public static ActorName getActorNameFromPartID(MovieName movie,
			String partID) {
		String id = partID.trim();
		if (movie.equals(MovieName.Map)) {
			if (id.equals("map_stripey_id8"))
				return ActorName.MStriped;
			else if (id.equals("map_green_id7"))
				return ActorName.MGreen;
			else if (id.equals("map_woman_id10"))
				return ActorName.WWalkingWhite;
			else if (id.equals("map_woman_id9"))
				return ActorName.WWalkingGreen;
			else if (id.equals("map_woman_id11"))
				return ActorName.WLastSunGlassesDress;
		} else if (movie.equals(MovieName.Newspaper)) {
			if (id.equals("newspaper_woman_id1"))
				return ActorName.WDeclining;
			else if (id.equals("newspaper_man_id2"))
				return ActorName.MOffering;
			else if (id.equals("newspaper_bg_id3"))
				return ActorName.MWithDog;
			else if (id.equals("newspaper_bg_id5"))
				return ActorName.MGlassesStaring;
			else if (id.equals("newspaper_bg_id4"))
				return ActorName.WWalkingJeansWhite;
			else if (id.equals("newspaper_bg_id6"))
				return ActorName.WWalkingYellowShirt;
			else if (id.equals("newspaper_bg_id7"))
				return ActorName.WLastWhiteSkirt;
		} else {
			return getPersonNameByID(id);
		}
		
		return null;
	}
	
	/**
	 * Get the actor's name from pose ground-truth track ID.
	 * @param movie The movie.
	 * @param partID The actor's ID from 3D tracks.
	 * @return The actor's name.
	 */
	public static ActorName getActorNameFromTrackID(MovieName movie,
			String trackID) {
		String id = trackID.trim().replaceAll("\\D+", "");	// remove all non-digits
		if (movie.equals(MovieName.Map)) {
			if (id.equals("1"))
				return ActorName.MStriped;
			else if (id.equals("2"))
				return ActorName.MGreen;
			else if (id.equals("3"))
				return ActorName.WWalkingWhite;
			else if (id.equals("4"))
				return ActorName.WWalkingGreen;
			else if (id.equals("5"))
				return ActorName.WDisappearingGray;
			else if (id.equals("6"))
				return ActorName.WLastSunGlassesDress;
		} else if (movie.equals(MovieName.Newspaper)) {
			if (id.equals("1"))
				return ActorName.WDeclining;
			else if (id.equals("2"))
				return ActorName.MOffering;
			else if (id.equals("3"))
				return ActorName.MGlassesStaring;
			else if (id.equals("4"))
				return ActorName.WWalkingJeansWhite;
			else if (id.equals("5"))
				return ActorName.WWalkingYellowShirt;
			else if (id.equals("6"))
				return ActorName.WLastWhiteSkirt;
		} else {
			return getPersonNameByID(id);
		}
		
		return null;
	}
	
	/**
	 * Count and return the number of lines in the file.
	 * @param file File to count.
	 * @return Number of lines contained in the specified file.
	 * @throws IOException
	 */
	public static int getNumLines(File file) throws IOException {
		InputStream is = new BufferedInputStream(new FileInputStream(file));
		try {
			byte[] c = new byte[1024];
			int count = 0;
			int readChars = 0;
			while ((readChars = is.read(c)) != -1) {
				for (int i = 0; i < readChars; ++i) {
					if (c[i] == '\n')
						++count;
				}
			}
			return count;
		} finally {
			is.close();
		}
	}
	
	/**
	 * Get the intervals when the real values in the given series _exceeds_
	 * the specified threshold (assuming time starts at frame 0).
	 * @param series Real value series.
	 * @param threshold Minimum threshold.
	 * @return Intervals during which the values in the series exceeded
	 * 	the given threshold.
	 */
	public static List<Interval> thresholdSeriesToIntervals(List<Double> series,
			double threshold) {
		List<Interval> intervals = new ArrayList<Interval>();
		Interval current = null;
		
		// Iterate over the time series
		for (int i = 0; i < series.size(); i++) {
			if (series.get(i) >= threshold) {
				if (current == null)
					current = new Interval(i, i+1);
				else
					current.setEnd(i+1);
				
			} else {
				if (current != null) {
					intervals.add(current);
					current = null;
				}
			}
		}
		
		// Clean up the last one
		if (current != null) {
			intervals.add(current);
		}
		
		return intervals;
	}

	/**
	 * Computes vector location and coordinate for missing frames. Coordinates and location of missing frames vectors are assigned infinite values.   
	 * @param gaze
	 */
	public static void interpolate(double[][] gaze) {
		int i = 0;
		int n = gaze.length;
		//go to first frame with a valued vector
		while(i < n && gaze[i][0] == Double.POSITIVE_INFINITY) i++;
		if (i == n) return;
		
		//copy this value to all previous vectors
		for(int j = 0; j < i; j++) System.arraycopy(gaze[i], 0, gaze[j], 0, 6);

		while (i < n){
			int lastIndex = i;
			i++;
			while(i < n && gaze[i][0] == Double.POSITIVE_INFINITY) i++;
			
			//if end copy last vector values to the last missing ones
			if (i == n) for(int j = lastIndex + 1; j < n; j++) System.arraycopy(gaze[lastIndex], 0, gaze[j], 0, 6);
			else{
				//else divide equally translations and rotations between valued vectors
				double[] t = new double[6];
				for(int k = 0; k < 6; k++) t[k] = (double) (gaze[i][k] - gaze[lastIndex][k]) / (i - lastIndex); 
				for (int j = lastIndex + 1; j < i; j++) for(int k = 0; k < 6; k++) gaze[j][k] = gaze[j - 1][k] + t[k];
			}
		}
	}

}
